import * as grpc from "@grpc/grpc-js"
import { DeletePostResponse } from "../../pb/post/DeletePostResponse"
import { GetPostRequest__Output } from "../../pb/post/GetPostRequest"
import { ListPostsRequest__Output } from "../../pb/post/ListPostsRequest"
import { Post } from "../../pb/post/Post"
import { CreatePostRequest__Output } from "../../pb/post/CreatePostRequest"
import { UpdatePostRequest__Output } from "../../pb/post/UpdatePostRequest"
import { PostResponse } from "../../pb/post/PostResponse"
import {
  createPost,
  deletePost,
  findAllPosts,
  findPost,
  findUniquePost,
  updatePost,
} from "../services/post.service"
import { DeletePostRequest__Output } from "../../pb/post/DeletePostRequest"
import { User } from "../../pb/user/User"

export const createPostHandler = async (
  req: grpc.ServerUnaryCall<CreatePostRequest__Output, PostResponse>,
  res: grpc.sendUnaryData<PostResponse>
) => {
  try {
    const operatorStr = req.metadata.get("user")[0] as string
    const operator = JSON.parse(operatorStr) as Pick<User, "id" | "role">

    const post = await createPost({
      title: req.request.title,
      content: req.request.content,
      image: req.request.image,
      category: req.request.category,
      published: true,
      authorId: operator.id!,
    })

    res(null, {
      post: {
        id: post.id,
        title: post.title,
        content: post.content,
        image: post.image,
        published: post.published,
        created_at: {
          seconds: post.created_at.getTime() / 1000,
        },
        updated_at: {
          seconds: post.updated_at.getTime() / 1000,
        },
      },
    })
  } catch (err: any) {
    if (err.code === "P2002") {
      res({
        code: grpc.status.ALREADY_EXISTS,
        message: "Post with that title already exists",
      })
    }
    res({
      code: grpc.status.INTERNAL,
      message: err.message,
    })
  }
}

export const updatePostHandler = async (
  req: grpc.ServerUnaryCall<UpdatePostRequest__Output, PostResponse>,
  res: grpc.sendUnaryData<PostResponse>
) => {
  try {
    const operatorStr = req.metadata.get("user")[0] as string
    const operator = JSON.parse(operatorStr) as Pick<User, "id" | "role">

    if (operator.id !== req.request.id)
      return res({
        code: grpc.status.PERMISSION_DENIED,
        message: "Require Author",
      })

    const postExists = await findPost({ id: req.request.id })

    if (!postExists) {
      res({
        code: grpc.status.NOT_FOUND,
        message: "No post with that ID exists",
      })
      return
    }
    const updatedPost = await updatePost(
      { id: req.request.id },
      {
        title: req.request.title,
        content: req.request.content,
        category: req.request.category,
        image: req.request.image,
        published: req.request.published,
      }
    )

    res(null, {
      post: {
        id: updatedPost.id,
        title: updatedPost.title,
        content: updatedPost.content,
        image: updatedPost.image,
        published: updatedPost.published,
        created_at: {
          seconds: updatedPost.created_at.getTime() / 1000,
        },
        updated_at: {
          seconds: updatedPost.updated_at.getTime() / 1000,
        },
      },
    })
  } catch (err: any) {
    res({
      code: grpc.status.INTERNAL,
      message: err.message,
    })
  }
}

export const findPostHandler = async (
  req: grpc.ServerUnaryCall<GetPostRequest__Output, PostResponse>,
  res: grpc.sendUnaryData<PostResponse>
) => {
  try {
    const post = await findUniquePost({ id: req.request.id })

    if (!post) {
      res({
        code: grpc.status.NOT_FOUND,
        message: "No post with that ID exists",
      })
      return
    }

    res(null, {
      post: {
        id: post.id,
        title: post.title,
        content: post.content,
        image: post.image,
        published: post.published,
        created_at: {
          seconds: post.created_at.getTime() / 1000,
        },
        updated_at: {
          seconds: post.updated_at.getTime() / 1000,
        },
      },
    })
  } catch (err: any) {
    res({
      code: grpc.status.INTERNAL,
      message: err.message,
    })
  }
}

export const deletePostHandler = async (
  req: grpc.ServerUnaryCall<DeletePostRequest__Output, DeletePostResponse>,
  res: grpc.sendUnaryData<DeletePostResponse>
) => {
  try {
    const operatorStr = req.metadata.get("user")[0] as string
    const operator = JSON.parse(operatorStr) as Pick<User, "id" | "role">

    if (operator.role !== "admin" && operator.id !== req.request.id)
      return res({
        code: grpc.status.PERMISSION_DENIED,
        message: "Require admin or Author",
      })

    const postExists = await findPost({ id: req.request.id })

    if (!postExists) {
      res({
        code: grpc.status.NOT_FOUND,
        message: "No post with that ID exists",
      })
      return
    }

    const post = await deletePost({ id: req.request.id })

    if (!post) {
      res({
        code: grpc.status.NOT_FOUND,
        message: "No post with that ID exists",
      })
      return
    }

    res(null, {
      success: true,
    })
  } catch (err: any) {
    res({
      code: grpc.status.INTERNAL,
      message: err.message,
    })
  }
}

export const findAllPostsHandler = async (
  call: grpc.ServerWritableStream<ListPostsRequest__Output, Post>
) => {
  try {
    const { page_no, page_size } = call.request
    const posts = await findAllPosts({
      page_no: parseInt(page_no),
      page_size: parseInt(page_size + ""),
    })

    for (let i = 0; i < posts.length; i++) {
      const post = posts[i]
      call.write({
        id: post.id,
        title: post.title,
        content: post.content,
        category: post.category,
        image: post.image,
        published: post.published,
        created_at: {
          seconds: post.created_at.getTime() / 1000,
        },
        updated_at: {
          seconds: post.updated_at.getTime() / 1000,
        },
      })
    }
    call.end()
  } catch (error: any) {
    console.log(error)
  }
}
